package com.xyzwps.lib.openapi.element;

import java.lang.reflect.Type;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

import static com.xyzwps.lib.openapi.element.Schema.*;

public class Api {
    private final String method;
    private final String path;
    private String summary;
    private String description;
    private boolean deprecated;
    private String operationId;

    private final List<String> tagNames = new ArrayList<>();

    private final List<Parameter> pathParams = new ArrayList<>();
    private final List<Parameter> headerParams = new ArrayList<>();
    private final List<Parameter> searchParams = new ArrayList<>();

    private Type requestBodyType;

    private Schema responseSchema;

    public Api(String method, String path) {
        this.method = method;
        this.path = path;
    }

    public void addRequestBody(Type type) {
        this.requestBodyType = type;
    }

    public void addResponseSchema(Schema schema) {
        this.responseSchema = schema;
    }

    public Parameter addHeaderParam(String name, Class<?> type) {
        var param = new Parameter(name, In.HEADER);
        // TODO: more properties
        headerParams.add(param);
        return param;
    }

    public Parameter addPathParam(String name, Class<?> type) {
        var param = new Parameter(name, In.PATH);
        // TODO: more properties
        pathParams.add(param);
        return param;
    }

    public Parameter addSearchParam(String name, Class<?> type) {
        var param = new Parameter(name, In.QUERY);
        // TODO: more properties
        searchParams.add(param);
        return param;
    }

    public void addTag(String name) {
        if (name != null && !name.isBlank()) {
            tagNames.add(name);
        }
    }

    public void addSummary(String summary) {
        this.summary = summary;
    }

    public void addDescription(String description) {
        this.description = description;
    }

    public void setDeprecated(boolean deprecated) {
        this.deprecated = deprecated;
    }

    public void setOperationId(String operationId) {
        this.operationId = operationId;
    }

    public void addToPaths(Paths paths) {
        var item = paths.item(this.path);
        if (item == null) {
            item = new PathItem();
        }

        var op = new Operation();
        op.operationId(isNotBlank(operationId) ? operationId : operationId(this.method, this.path));

        for (var tagName : tagNames) {
            op.addTag(tagName);
        }

        if (isNotBlank(this.summary)) {
            op.summary(this.summary);
        }

        if (isNotBlank(this.description)) {
            op.description(this.description);
        }

        if (this.deprecated) {
            op.deprecated(true);
        }

        pathParams.forEach(op::addParameter);
        headerParams.forEach(op::addParameter);
        searchParams.forEach(op::addParameter);

        // TODO: more properties
        var responses = new Responses()
                .addStatusResponse(200, new Response("Succeed response schema")
                        .addContent("application/json", new MediaType() // TODO: 支持其他类型
                                .schema(responseSchema == null ? new ObjectSchema() : responseSchema)))
                .responseDefault(new Response("Default Response Schema")
                        .addContent("application/json", new MediaType()
                                .schema(new ObjectSchema()
                                        .addProperty("code", new StringSchema())
                                        .addProperty("msg", new StringSchema()))));

        op.responses(responses);

        if (requestBodyType != null) {
            // TODO: 支持常见泛型
            var bodySchema = new Schema.RefSchema("#/components/schemas/" + canonicalName(requestBodyType));
            var mediaType = new MediaType().schema(bodySchema);
            var requestBody = new RequestBody().addToContent("application/json", mediaType);
            op.requestBody(requestBody);
        }

        switch (this.method.toUpperCase()) {
            case "GET" -> item.get(op);
            case "POST" -> item.post(op);
            case "PUT" -> item.put(op);
            case "DELETE" -> item.delete(op);
            case "HEAD" -> item.head(op);
            case "PATCH" -> item.patch(op);
            case "TRACE" -> item.trace(op);
            default -> throw new IllegalArgumentException("Unsupported method: " + this.method);
        }

        paths.add(this.path, item);
    }

    private static final MessageDigest md;

    static {
        try {
            md = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("MD5 algorithm not found", e);
        }
    }

    private static boolean isNotBlank(String s) {
        return s != null && !s.isBlank();
    }

    public static String operationId(String method, String path) {
        return (method + path).replace('/', '~');
    }

    private static String canonicalName(Type type) {
        return switch (type) {
            case Class<?> c -> c.getCanonicalName();
            default -> throw new UnsupportedOperationException();
        };
    }
}
